using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Storage;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace TileEngine
{
    public static class TileMap
    {
        #region Fields
        public const int TileWidth = 48;
        public const int TileHeight = 48;
        public const int MapWidth = 200;
        public const int MapHeight = 60;
        public const int MapLayers = 6;
        private const int backgroundTile = 0;

        static private MapSquare[,] mapCells = new MapSquare[MapWidth, MapHeight];               

        public static bool EditorMode = false;
        public static bool SelectTileMode = false;

        public static Color tintColor = new Color(255, 255, 255);
        public static int tileRotation = 0;

        public static SpriteFont spriteFont;
        static public Texture2D currentTileSheet;
        public static List<Texture2D> tileSheets = new List<Texture2D>();
        #endregion

        #region Initialization
        static public void Initialize(List<Texture2D> a_tileSheets)
        {
            tileSheets = a_tileSheets;
            currentTileSheet = tileSheets.First();

            for (int x = 0; x < MapWidth; x++)
            {
                for (int y = 0; y < MapHeight; y++)
                {
                    for (int z = 0; z < MapLayers; z++)
                    {
                        LayerData[] layerData = new LayerData[MapLayers];
                        for (int i = 0; i < layerData.Length; i++)
			            {
                            LayerData tmpData = new LayerData();
			                if (i == 0)
                            {
                                tmpData.Layer = backgroundTile;
                                tmpData.Rotation = tileRotation;
                                tmpData.Color = tintColor;
                                layerData[i] = tmpData;
                            }
                            else
                            {
                                tmpData.Layer = 0;
                                tmpData.Rotation = tileRotation;
                                tmpData.Color = tintColor;
                                layerData[i] = tmpData;
                            }
			            }
                        mapCells[x, y] = new MapSquare(layerData, null, false, false, tintColor);
                    }
                }
            }
        }
        #endregion

        #region Tile and Tile Sheet Handling
        public static int TilesPerRow
        {
            get { return currentTileSheet.Width / TileWidth; }
        }

        public static Rectangle TileSourceRectangle(int a_tileIndex)
        {
            return new Rectangle(
                (a_tileIndex % TilesPerRow) * TileWidth,
                (a_tileIndex / TilesPerRow) * TileHeight,
                TileWidth,
                TileHeight);
        }

        public static void LoadTileSheet(FileStream a_fs, GraphicsDevice a_graphicsDevice)
        {
            AddTileSheet(Texture2D.FromStream(a_graphicsDevice, a_fs));
        }

        private static void AddTileSheet(Texture2D a_tileSheet)
        {
            foreach (Texture2D sheet in tileSheets)
	        {
		        if (a_tileSheet == sheet)
	            {
		            return;
	            }
	        }
            tileSheets.Add(a_tileSheet);
            currentTileSheet = tileSheets[tileSheets.Count - 1];
        }

        public static void setCurrentTileSheet(int a_index)
        {
            currentTileSheet = tileSheets[a_index];
        }
        #endregion

        #region Information about Map Cells
        public static int GetCellByPixelX(int a_pixelX)
        {
            return a_pixelX / TileWidth;
        }

        public static int GetCellByPixelY(int a_pixelY)
        {
            return a_pixelY / TileHeight;
        }

        public static Vector2 GetCellByPixel(Vector2 a_pixelPos)
        {
            return new Vector2(
                GetCellByPixelX((int)a_pixelPos.X),
                GetCellByPixelY((int)a_pixelPos.Y));
        }

        public static Vector2 GetCellCenter(int a_cellX, int a_cellY)
        {
            return new Vector2(
                (a_cellX * TileWidth) + (TileWidth / 2),
                (a_cellY * TileHeight) + (TileHeight / 2));
        }

        public static Vector2 GetCellCenter(Vector2 a_cell)
        {
            return GetCellCenter(
                (int)a_cell.X,
                (int)a_cell.Y);
        }

        public static Rectangle CellWorldRectangle(int a_cellX, int a_cellY)
        {
            return new Rectangle(
                a_cellX * TileWidth,
                a_cellY * TileHeight,
                TileWidth,
                TileHeight);
        }

        public static Rectangle CellWorldRectangle(Vector2 a_cell)
        {
            return CellWorldRectangle(
                (int)a_cell.X,
                (int)a_cell.Y);
        }

        public static Rectangle CellScreenRectangle(int a_cellX, int a_cellY)
        {     
            return Camera.WorldToScreen(CellWorldRectangle(a_cellX, a_cellY));
        }

        public static Rectangle CellScreenRectangle(Vector2 a_cell)
        {     
            return Camera.WorldToScreen(CellWorldRectangle((int)a_cell.X, (int)a_cell.Y));
        }

        public static bool CellIsBlocked(int a_cellX, int a_cellY)
        {
            MapSquare square = GetMapSquareAtCell(a_cellX, a_cellY);
            if (square == null)
            {
                return false;
            }
            else
            {
                return square.m_blocked;
            }
        }

        public static bool CellIsBlocked(Vector2 a_cell)
        {
            return CellIsBlocked((int)a_cell.X, (int)a_cell.Y);
        }

        public static bool CellIsSelected(int a_cellX, int a_cellY)
        {
            MapSquare square = GetMapSquareAtCell(a_cellX, a_cellY);
            if (square == null)
            {
                return false;
            }
            else
            {
                return square.m_selected;
            }
        }

        public static bool CellIsSelected(Vector2 a_cell)
        {
            return CellIsSelected((int)a_cell.X, (int)a_cell.Y);
        }

        public static bool CellIsBlockedByPixel(Vector2 a_pixelPos)
        {
            return CellIsBlocked(
                GetCellByPixelX((int)a_pixelPos.X),
                GetCellByPixelY((int)a_pixelPos.Y));
        }

        public static bool CellIsSelectedByPixel(Vector2 a_pixelPos)
        {
            return CellIsSelected(
                GetCellByPixelX((int)a_pixelPos.X),
                GetCellByPixelY((int)a_pixelPos.Y));
        }

        public static string CellTypeValue(int a_cellX, int a_cellY)
        {
            MapSquare square = GetMapSquareAtCell(a_cellX, a_cellY);
            if (square == null)
            {
                return "";
            }
            else 
            {
                return square.m_attributes[0];
            }
        }

        public static string CellTypeValue(Vector2 a_cell)
        {
            return CellTypeValue((int)a_cell.X, (int)a_cell.Y);
        }

        public static string CellName(int a_cellX, int a_cellY)
        {
            MapSquare square = GetMapSquareAtCell(a_cellX, a_cellY);
            if (square == null)
            {
                return "";
            }
            else
            {
                return square.m_attributes[1];
            }
        }

        public static string CellName(Vector2 a_cell)
        {
            return CellName((int)a_cell.X, (int)a_cell.Y);
        }
        #endregion

        #region Information about Map Square Objects
        public static MapSquare GetMapSquareAtCell(int a_tileX, int a_tileY)
        {
            if ((a_tileX >= 0) && (a_tileX < MapWidth) &&
                (a_tileY >= 0) && (a_tileY < MapHeight))
            {
                return mapCells[a_tileX, a_tileY];
            }
            else
            {
                return null;
            }
        }

        public static void setMapSquareAtCell(int a_tileX, int a_tileY, MapSquare a_tile)
        {
            if ((a_tileX >= 0) && (a_tileX < MapWidth) &&
                (a_tileY >= 0) && (a_tileY < MapHeight))
            {
                mapCells[a_tileX, a_tileY] = a_tile;
            }
           
        }

        public static void SetTileAtCell(int a_tileX, int a_tileY, int a_layer, int a_tileIndex, string a_tileName)
        {
            if ((a_tileX >= 0) && (a_tileX < MapWidth) &&
                (a_tileY >= 0) && (a_tileY < MapHeight))
            {
                mapCells[a_tileX, a_tileY].m_layerData[a_layer].Layer = a_tileIndex;
                mapCells[a_tileX, a_tileY].m_layerData[a_layer].Rotation = tileRotation;
                mapCells[a_tileX, a_tileY].m_layerData[a_layer].Color = tintColor;   
             
                // namnet borde kanske ligga i m_layerData...
                mapCells[a_tileX, a_tileY].m_attributes[1] = a_tileName;
            }
        }

        public static MapSquare GetMapSquareAtPixel(int  a_pixelX, int a_pixelY)
        {
            return GetMapSquareAtCell(
                GetCellByPixelX(a_pixelX),
                GetCellByPixelY(a_pixelY));
        }

        public static MapSquare GetMapSquareAtPixel(Vector2 a_pixelPos)
        {
            return GetMapSquareAtPixel(
                (int)a_pixelPos.X,
                (int)a_pixelPos.Y);
        }
        #endregion

        #region Drawing
        public static void Draw(SpriteBatch a_spriteBatch)
        {
            int startX = GetCellByPixelX((int)Camera.Position.X);
            int endX = GetCellByPixelX((int)Camera.Position.X + Camera.ViewPortWidth);

            int startY = GetCellByPixelY((int)Camera.Position.Y);
            int endY = GetCellByPixelY((int)Camera.Position.Y + Camera.ViewPortHeight);

            Vector2 origin;

            for (int x = startX; x <= endX; x++)
            {
                for (int y = startY; y <= endY; y++)
                {
                    for (int z = 0; z < MapLayers; z++)
                    {
                        if ((x >= 0) && (y >= 0) &&
                            (x < MapWidth) && (y < MapHeight))
                        {
                            if (mapCells[x, y].m_layerData[z].Rotation == 90)
                            {
                                origin = new Vector2(0, TileHeight);
                            }
                            else if (mapCells[x, y].m_layerData[z].Rotation == 180)
                            {
                                origin = new Vector2(TileWidth, TileHeight);
                            }
                            else if (mapCells[x, y].m_layerData[z].Rotation == 270)
                            {
                                origin = new Vector2(TileWidth, 0);
                            }
                            else
                            {
                                origin = Vector2.Zero;
                            }

                            a_spriteBatch.Draw(
                                currentTileSheet,
                                CellScreenRectangle(x, y),
                                TileSourceRectangle(mapCells[x, y].m_layerData[z].Layer),
                                mapCells[x, y].m_layerData[z].Color,
                                MathHelper.ToRadians(mapCells[x, y].m_layerData[z].Rotation),
                                origin,
                                SpriteEffects.None,
                                1.0f - ((float)z * 0.1f));                            
                        }
                    }

                    if (EditorMode)
                    {
                        DrawEditModeItems(a_spriteBatch, x, y);
                    }
                }
            }
        }

        public static void DrawEditModeItems(SpriteBatch a_spriteBatch, int a_x, int a_y)
        {
            if ((a_x < 0) || (a_x >= MapWidth) ||
                (a_y < 0) || (a_y >= MapHeight))
            {
                return;
            }

            if (CellIsBlocked(a_x, a_y))
            {
                a_spriteBatch.Draw(
                    tileSheets.Last(),
                    CellScreenRectangle(a_x, a_y),
                    TileSourceRectangle(0),
                    new Color(120, 0, 0, 100),
                    0.0f,
                    Vector2.Zero,
                    SpriteEffects.None,
                    0.0f);
            }

            if (CellIsSelected(a_x, a_y))
            {
                a_spriteBatch.Draw(
                    tileSheets.Last(),
                    CellScreenRectangle(a_x, a_y),
                    TileSourceRectangle(1),
                    new Color(0, 120, 0, 100),
                    0.0f,
                    Vector2.Zero,
                    SpriteEffects.None,
                    0.0f);
            }

            if (mapCells[a_x, a_y].m_attributes[0] != "")
            {
                Rectangle screenRect = CellScreenRectangle(a_x, a_y);

                a_spriteBatch.DrawString(
                    spriteFont,
                    mapCells[a_x, a_y].m_attributes[0],
                    new Vector2(screenRect.X, screenRect.Y),
                    Color.White,
                    0.0f,
                    Vector2.Zero,
                    0.6f,
                    SpriteEffects.None,
                    0.0f);
            }
        }
        #endregion

        #region Loading and Saving Maps
        public static void SaveMap(FileStream a_filestream)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(a_filestream, mapCells);
            a_filestream.Close();
        }

        public static void LoadMap(FileStream a_filestream)
        {
            try
            {
                BinaryFormatter formatter = new BinaryFormatter();
                mapCells = (MapSquare[,])formatter.Deserialize(a_filestream);

                if (mapCells.Length != (MapWidth * MapHeight))
                {
                    mapCells = convertMap(mapCells);
                }

                a_filestream.Close();
            }
            catch
            {
                ClearMap();
            }
        }

        public static void ClearMap()
        {
            for (int x = 0; x < MapWidth; x++)
            {
                for (int y = 0; y < MapHeight; y++)
                {
                    for (int z = 0; z < MapLayers; z++)
                    {
                        mapCells[x, y] = new MapSquare(null, null, false, false, Color.White);
                    }
                }
            }
        }

        public static void ClearTile(int x, int y)
        {
            mapCells[GetCellByPixelX(x), GetCellByPixelY(y)] = new MapSquare(null, null, false, false, Color.White);
        }
        
        #endregion

        #region Helper Methods
        //used to convert old maps after MapSquare has changed
        private static MapSquare[,] convertMap(MapSquare[,] a_mapCells)
        {
            MapSquare[,] newMapCells = new MapSquare[MapWidth, MapHeight];
            for (int x = 0; x < MapWidth; x++)
            {
                for (int y = 0; y < MapHeight; y++)
                {
                    for (int z = 0; z < MapLayers; z++)
                    {
                        if (mapCells.GetLength(0) > x && mapCells.GetLength(1) > y)
	                    {
		                    newMapCells[x, y] = mapCells[x, y];
	                    }                                                    
                        else
                        {        
                            newMapCells[x, y] = new MapSquare(null, null, false, false, Color.White); 
                        }                                                                                         
                    }
                }
            }

            Console.WriteLine("The TileEngine mapsize has changed.");
            Console.WriteLine("Converting current Map to new size.");
                               
            return newMapCells;
        }
        #endregion
    }
}
